1 /** 2 * License: 3 * Copyright Devisualization (Richard Andrew Cattermole) 2014 - 2017. 4 * Distributed under the Boost Software License, Version 1.0. 5 * (See accompanying file LICENSE_1_0.txt or copy at 6 * http://www.boost.org/LICENSE_1_0.txt) 7 */ 8 module devisualization.util.core.linegraph; 9 10 /** 11 * A line graph. 12 * Given points produces all points upon the line. 13 * 14 * Does not support decimal (float) points. 15 * 16 * Could be rather useful for Devisualization.Image filters perhaps? 17 */ 18 deprecated("Use PointsGenerator instead.") 19 struct LineGraphType(bool checkNegative, TYPE) { 20 private { 21 enum GraphFunction { 22 SplineCurve, 23 BezierCurve, 24 Line, 25 Point 26 } 27 28 TYPE[] coordsx; 29 TYPE[] coordsy; 30 GraphFunction[] functions; 31 32 TYPE[][] xPointBezierCurve; 33 TYPE[][] yPointBezierCurve; 34 float[] splineCurveTensions; 35 } 36 37 /** 38 * Initiate with starting point 39 * Acts as a lineTo 40 * 41 * Params: 42 * startx = Starting x coord 43 * starty = Strating y coord 44 */ 45 this(TYPE startx = 0, TYPE starty = 0) { 46 pointAt(startx, starty); 47 } 48 49 /** 50 * Adds a straight line. 51 * 52 * Params: 53 * x = To position 54 * y = To position 55 */ 56 void lineTo(TYPE x, TYPE y) { 57 coordsx ~= x; 58 coordsy ~= y; 59 functions ~= GraphFunction.Line; 60 } 61 62 /** 63 * Adds a posize_t line. 64 * 65 * Params: 66 * x = To position 67 * y = To position 68 */ 69 void pointAt(TYPE x, TYPE y) { 70 coordsx ~= x; 71 coordsy ~= y; 72 functions ~= GraphFunction.Point; 73 } 74 75 /** 76 * Adds a bezier curve. 77 * 78 * Params: 79 * x = To position 80 * y = To position 81 * xPoints = Points to manipulate by 82 * yPoints = Points to manipulate by 83 */ 84 void bezierCurveTo(TYPE x, TYPE y, TYPE[] xPoints, TYPE[] yPoints) { 85 coordsx ~= x; 86 coordsy ~= y; 87 functions ~= GraphFunction.BezierCurve; 88 xPointBezierCurve ~= xPoints; 89 yPointBezierCurve ~= yPoints; 90 } 91 92 /** 93 * Adds a bezier curve. 94 * 95 * Params: 96 * x = To position 97 * y = To position 98 * x2 = Point to manipulate by 99 * y2 = Point to manipulate by 100 */ 101 void bezierCurveTo(TYPE x, TYPE y, TYPE x2, TYPE y2) { 102 coordsx ~= x; 103 coordsy ~= y; 104 functions ~= GraphFunction.BezierCurve; 105 xPointBezierCurve ~= [x2]; 106 yPointBezierCurve ~= [y2]; 107 } 108 109 /** 110 * Adds a bezeier curve using points to derive the control points 111 * 112 * Params: 113 * x = To position 114 * y = To position 115 * x2 = Point to go through 116 * y2 = Point to go through 117 * tension = Tension for the curve 118 */ 119 void splineThrough(TYPE x, TYPE y, TYPE x2, TYPE y2, float tension) { 120 coordsx ~= x; 121 coordsy ~= y; 122 functions ~= GraphFunction.SplineCurve; 123 xPointBezierCurve ~= [x2]; 124 yPointBezierCurve ~= [y2]; 125 splineCurveTensions ~= tension; 126 } 127 128 /** 129 * Get all points upon the line 130 * 131 * Params: 132 * del = Delegate to call with a point 133 * step = The difference between points. Default: 1 134 */ 135 void apply(void delegate(TYPE x, TYPE y) del, size_t step = 1) { 136 void d(TYPE x, TYPE y, float w) { 137 del(x, y); 138 } 139 140 applyFunc!(checkNegative, TYPE)(this, &d, step); 141 } 142 143 /** 144 * Get all points upon the line 145 * Includes weighting for the point. Ranges from 0f .. 1f 146 * 147 * Params: 148 * del = Delegate to call with a point 149 * step = The difference between points. Default: 1 150 */ 151 void apply(void delegate(TYPE x, TYPE y, float weighting) del, size_t step = 1) { 152 applyFunc!(checkNegative, TYPE)(this, del, step); 153 } 154 } 155 156 alias LineGraph = LineGraphType!(true, size_t); 157 alias SignedLineGraph = LineGraphType!(false, ptrdiff_t); 158 159 private { 160 void applyFunc(bool checkNeg, TYPE, GRAPH)(GRAPH graph, void delegate(TYPE x, TYPE y, float w) del, size_t step = 1) { 161 import std.math : floor, ceil, sin, cos, sqrt, PI, abs, pow; 162 import std.algorithm : swap; 163 164 with(graph) { 165 TYPE lastx = coordsx[0]; 166 TYPE lasty = coordsy[0]; 167 size_t currentBezierCurve, currentSpline; 168 del(lastx, lasty, 1f); 169 170 foreach(i, func; functions) { 171 TYPE x = coordsx[i]; 172 TYPE y = coordsy[i]; 173 174 TYPE newx = lastx; 175 TYPE newy = lasty; 176 177 if (func == GraphFunction.Line) { 178 // http://en.wikipedia.org/wiki/Xiaolin_Wu%27s_line_algorithm 179 // modified to force the start and end values to be 100% weighting 180 181 void plot(TYPE xx, TYPE yy, float w) { 182 // is pixel in range? 183 // algo can go outside 184 // FIXME: 185 /*if (y > lasty && yy <= y && yy >= lasty) { 186 if (x > lastx && xx <= x && xx >= lastx) { 187 del(xx, yy, w); 188 } 189 }*/ 190 del(xx, yy, w); 191 } 192 193 TYPE ipart(float x) { return cast(TYPE)x; } // integer part of x 194 TYPE round(float x) { return ipart(x + 0.5); } 195 float fpart(float x) { return (x < 0) ? 196 (1f - (x - x.floor)) : 197 (x - x.floor); } // fractional part of x 198 float rfpart(float x) { return 1 - fpart(x); } 199 200 void drawLine(TYPE x0, TYPE y0, TYPE x1, TYPE y1) { 201 bool steep = abs(y1 - y0) > abs(x1 - x0); 202 if (steep) { 203 swap(x0, y0); 204 swap(x1, y1); 205 } 206 if (x0 > x1) { 207 swap(x0, x1); 208 swap(y0, y1); 209 } 210 211 TYPE dx = x1 - x0; 212 TYPE dy = y1 - y0; 213 float gradient = (cast(float)dy / cast(float)dx); 214 215 // handle first endpoint 216 TYPE xend = round(x0); 217 float yend = y0 + gradient * (xend - x0); 218 float xgap = rfpart(x0 + 0.5f); 219 TYPE xpxl1 = xend; // this will be used in the main loop 220 TYPE ypxl1 = ipart(yend); 221 if (steep) { 222 //plot(ypxl1, xpxl1, 1f); // doubled up 223 plot(ypxl1+1, xpxl1, fpart(yend) * xgap); 224 } else { 225 //plot(xpxl1, ypxl1, 1f); // doubled up 226 plot(xpxl1, ypxl1+1, fpart(yend) * xgap); 227 } 228 float intery = yend + gradient; // first y-intersection for the main loop 229 230 // handle second endpoint 231 xend = round(x1); 232 yend = y1 + gradient * (xend - x1); 233 xgap = fpart(x1 + 0.5); 234 TYPE xpxl2 = xend; //this will be used in the main loop 235 TYPE ypxl2 = ipart(yend); 236 if (steep) { 237 plot(ypxl2, xpxl2, 1f); 238 plot(ypxl2+1, xpxl2, fpart(yend) * xgap); 239 } else { 240 plot(xpxl2, ypxl2, rfpart(yend) * xgap); 241 plot(xpxl2, ypxl2+1, 2f); 242 } 243 244 // main loop 245 foreach(x; xpxl1 + 1 .. xpxl2) { 246 if (steep) { 247 plot(ipart(intery), x, rfpart(intery)); 248 plot(ipart(intery)+1, x, fpart(intery)); 249 } else { 250 plot(x, ipart(intery), rfpart(intery)); 251 plot(x, ipart(intery)+1, fpart(intery)); 252 } 253 intery += gradient * step; 254 } 255 } 256 257 drawLine(lastx, lasty, x, y); 258 lastx = x; 259 lasty = y; 260 } else if (func == GraphFunction.Point) { 261 del(x, y, 1f); 262 lastx = x; 263 lasty = y; 264 } else if (func == GraphFunction.BezierCurve || func == GraphFunction.SplineCurve) { 265 //heavily based upon http://www.codeproject.com/Articles/25237/Bezier-Curves-Made-Simple 266 267 TYPE[] xPoints = xPointBezierCurve[currentBezierCurve]; 268 TYPE[] yPoints = yPointBezierCurve[currentBezierCurve]; 269 270 if (func == GraphFunction.SplineCurve) { 271 TYPE[] xPoints2; 272 TYPE[] yPoints2; 273 xPoints2.length = xPoints.length + 1; 274 yPoints2.length = yPoints.length + 1; 275 276 float tension = splineCurveTensions[currentSpline]; 277 278 size_t poi; 279 foreach(j, xp; xPoints) { 280 TYPE yp = yPoints[j]; 281 TYPE xpp, ypp; 282 283 if (j == xPoints.length - 1) { 284 xpp = x; 285 ypp = y; 286 } else { 287 xpp = x; 288 ypp = y; 289 } 290 291 auto splineControlPoints = getSplineControlPoints(lastx, lasty, xp, yp, xpp, ypp, tension); 292 xPoints2[poi] = splineControlPoints[0]; 293 yPoints2[poi] = splineControlPoints[1]; 294 xPoints2[poi + 1] = splineControlPoints[2]; 295 yPoints2[poi + 1] = splineControlPoints[3]; 296 297 poi += 2; 298 } 299 300 xPoints = [lastx] ~ xPoints2 ~ [x]; 301 yPoints = [lasty] ~ yPoints2 ~ [y]; 302 currentSpline++; 303 } else { 304 xPoints = [lastx] ~ xPoints ~ [x]; 305 yPoints = [lasty] ~ yPoints ~ [y]; 306 } 307 308 currentBezierCurve++; 309 310 double Ni(size_t n, size_t i) { 311 import devisualization.util.core.math : factorial; 312 313 double ni; 314 double a1 = factorial(n); 315 double a2 = factorial(i); 316 double a3 = factorial(n - i); 317 ni = a1 / (a2 * a3); 318 return ni; 319 } 320 321 double Bernstein(size_t n, size_t i, double t) { 322 double basis; 323 double ti; /* t^i */ 324 double tni; /* (1 - t)^i */ 325 326 /* Prevent problems with pow */ 327 328 if (t == 0.0 && i == 0) 329 ti = 1.0; 330 else 331 ti = pow(t, i); 332 333 if (n == i && t == 1.0) 334 tni = 1.0; 335 else 336 tni = pow((1 - t), (n - i)); 337 338 //Bernstein basis 339 basis = Ni(n, i) * ti * tni; 340 return basis; 341 } 342 343 void Bezier2D(size_t cpts) { 344 size_t npts = xPoints.length; 345 double t; 346 double step2 = 1f / (cpts - 1); 347 348 // Calculate points on curve 349 350 t = 0; 351 352 for (size_t j = 0; j < cpts; j++) { 353 if ((1.0 - t) < 5e-6) 354 t = 1.0; 355 356 double xout = 0; 357 double yout = 0; 358 359 for (size_t k = 0; k < npts; k++) { 360 double basis = Bernstein(npts - 1, k, t); 361 xout += basis * xPoints[k]; 362 yout += basis * yPoints[k]; 363 } 364 365 if (!(newx == cast(TYPE)xout && newy == cast(TYPE)yout)) 366 del(cast(TYPE)xout, cast(TYPE)yout, 1f); 367 newx = cast(TYPE)xout; 368 newy = cast(TYPE)yout; 369 370 t += step2; 371 } 372 } 373 374 size_t xPointsS; 375 if (lastx > x) 376 xPointsS = lastx - x; 377 else 378 xPointsS = x - lastx; 379 size_t yPointsS; 380 if (lasty > y) 381 yPointsS = lasty - y; 382 else 383 yPointsS = y - lasty; 384 Bezier2D(xPointsS * yPointsS); 385 386 lastx = x; 387 lasty = y; 388 } 389 } 390 } 391 } 392 393 TYPE[4] getSplineControlPoints(TYPE)(TYPE x0, TYPE y0, TYPE x1, TYPE y1, TYPE x2, TYPE y2, float t){ 394 // from http://scaledinnovation.com/analytics/splines/aboutSplines.html 395 import std.math : sqrt, pow; 396 397 real d01 = sqrt(pow(x1 - x0, 2f) + pow(y1 - y0, 2f)); 398 real d12 = sqrt(pow(x2 - x1, 2f) + pow(y2 - y1, 2f)); 399 real fa = t * d01 / (d01 + d12); // scaling factor for triangle Ta 400 real fb = t * d12 / (d01 + d12); // ditto for Tb, simplifies to fb=t-fa 401 TYPE p1x = cast(TYPE)(x1 - fa * (x2 - x0)); // x2-x0 is the width of triangle T 402 TYPE p1y = cast(TYPE)(y1 - fa * (y2 - y0)); // y2-y0 is the height of T 403 TYPE p2x = cast(TYPE)(x1 + fb * (x2 - x0)); 404 TYPE p2y = cast(TYPE)(y1 + fb * (y2 - y0)); 405 return [p1x, p1y, p2x, p2y]; 406 } 407 } 408 409 /** 410 * A line graph. 411 * Given points produces all points upon the line. 412 * 413 * Uses the GC. 414 */ 415 struct PointsGenerator(Point) if (__traits(compiles, { 416 Point p; 417 double 418 x = p.x, 419 y = p.y, 420 z = p.z; 421 p.x = x; 422 p.y = y; 423 p.z = z; 424 })) { 425 426 private { 427 Segment!Point[] segments; 428 Point startAt; 429 } 430 431 this(Point startAt) { 432 import std.math : isNaN; 433 434 if (startAt.x.isNaN) 435 startAt.x = 0; 436 if (startAt.y.isNaN) 437 startAt.y = 0; 438 if (startAt.z.isNaN) 439 startAt.z = 0; 440 441 this.startAt = startAt; 442 } 443 444 void pointAt(Point location) { 445 import std.math : isNaN; 446 447 if (location.x.isNaN) 448 location.x = 0; 449 if (location.y.isNaN) 450 location.y = 0; 451 if (location.z.isNaN) 452 location.z = 0; 453 454 segments ~= Segment!Point(Segment!Point.Type.Point, location); 455 } 456 457 void lineTo(Point location) { 458 import std.math : isNaN; 459 460 if (location.x.isNaN) 461 location.x = 0; 462 if (location.y.isNaN) 463 location.y = 0; 464 if (location.z.isNaN) 465 location.z = 0; 466 467 segments ~= Segment!Point(Segment!Point.Type.LineTo, location); 468 } 469 470 void bezierOf(Point location, Point[] control...) { 471 if (control.length == 0) { 472 // basically is a straight line, 473 // but bezier curve evaluation is slow compared to lineTo. 474 lineTo(location); 475 // hence we switch to it instead. 476 } else { 477 segments ~= Segment!Point(Segment!Point.Type.Bezier, location); 478 segments[$-1].bezier_points = control.dup; 479 } 480 } 481 482 void arcOf(Point location, Point arc_radius) { 483 segments ~= Segment!Point(Segment!Point.Type.Arc, location); 484 segments[$-1].arc_radius = arc_radius; 485 } 486 487 auto generate(double interval) { 488 struct Result { 489 Segment!Point[] segments; 490 immutable(double) interval; 491 Point next, current; 492 493 this(Segment!Point[] segments, double interval, Point startAt) { 494 this.segments = segments; 495 this.interval = interval; 496 next = startAt; 497 498 if (segments.length > 0) 499 segments[0].initialize(interval, next); 500 next = startAt; 501 } 502 503 @property { 504 pragma(inline, true) 505 Point front() { 506 return next; 507 } 508 509 pragma(inline, true) 510 bool empty() { 511 return segments.length == 0 || next == Point.init; 512 } 513 } 514 515 void popFront() { 516 import std.math : isNaN; 517 current = next; 518 next = Point.init; 519 520 while(segments.length > 0) { 521 Point ret = segments[0].nextCoord(interval, current); 522 523 if (ret.x.isNaN || ret.y.isNaN || ret.z.isNaN) { 524 segments = segments[1 .. $]; 525 if (segments.length > 0) 526 segments[0].initialize(interval, current); 527 } else { 528 next = ret; 529 break; 530 } 531 } 532 } 533 } 534 535 return Result(segments.dup, interval, startAt); 536 } 537 } 538 539 private { 540 struct Segment(Point) { 541 Type type; 542 Point location, arc_radius; 543 Point[] bezier_points; 544 545 void initialize(double interval, ref Point previous) { 546 import std.math : abs, copysign, pow, PI, PI_2, sqrt; 547 548 switch(type) { 549 case Type.LineTo: 550 dx = location.x - previous.x; 551 dy = location.y - previous.y; 552 dz = location.z - previous.z; 553 554 Point point_abs = Point(abs(dx), abs(dy), abs(dz)); 555 double point_sum_third = (point_abs.x + point_abs.y + point_abs.z) * (1/3f); 556 Point point_portion = Point( 557 point_abs.x / point_sum_third, 558 point_abs.y / point_sum_third, 559 point_abs.z / point_sum_third); 560 561 lineTo_delta = Point( 562 copysign(point_portion.x, dx) * interval, 563 copysign(point_portion.y, dy) * interval, 564 copysign(point_portion.z, dz) * interval); 565 break; 566 567 case Type.Arc: 568 // This code is surprisingly like lineTo. 569 570 // Required to calculate starting angles (to the quartent) of the curve. 571 // in lineTo it only needs 'effective' difference, where as here we 572 // need full correct difference with understanding of negatives. 573 dx = calculateDifferenceOnAxis(location.x, previous.x); 574 dy = calculateDifferenceOnAxis(location.y, previous.y); 575 dz = calculateDifferenceOnAxis(location.z, previous.z); 576 577 arc_center = Point( 578 (location.x + previous.x)/2, 579 (location.y + previous.y)/2, 580 (location.z + previous.z)/2); 581 arc_radius = Point( 582 abs(arc_center.x-location.x), 583 abs(arc_center.y-location.y), 584 abs(arc_center.z-location.z)); 585 586 double train_error1, train_error2, 587 current_angle1, current_angle2; 588 589 // x 590 591 if (dx > 0) { 592 current_angle1 = PI; 593 current_angle2 = PI+PI_2; 594 } else { 595 current_angle1 = 0; 596 current_angle2 = PI_2; 597 } 598 599 train_error1 = 0; 600 train_error2 = 0; 601 602 while(train_NN(current_angle1, train_error1, 0.1, arc_radius.x, arc_center.x, previous.x)) {} 603 while(train_NN(current_angle2, train_error2, 0.1, arc_radius.x, arc_center.x, location.x)) {} 604 605 if (current_angle1 > current_angle2 && dx > 0) { 606 arc_angle.x = (2*PI) - current_angle1; 607 arc_maxangle.x = (2*PI) - current_angle2; 608 } else { 609 arc_angle.x = current_angle1; 610 arc_maxangle.x = current_angle2; 611 } 612 613 // x 614 // y 615 616 if (dy > 0) { 617 current_angle1 = PI; 618 current_angle2 = PI+PI_2; 619 } else { 620 current_angle1 = 0; 621 current_angle2 = PI_2; 622 } 623 624 train_error1 = 0; 625 train_error2 = 0; 626 627 while(train_NN(current_angle1, train_error1, 0.1, arc_radius.y, arc_center.y, previous.y)) {} 628 while(train_NN(current_angle2, train_error2, 0.1, arc_radius.y, arc_center.y, location.y)) {} 629 630 if (current_angle1 > current_angle2 && dy > 0) { 631 arc_angle.y = (2*PI) - current_angle1; 632 arc_maxangle.y = (2*PI) - current_angle2; 633 } else { 634 arc_angle.y = current_angle1; 635 arc_maxangle.y = current_angle2; 636 } 637 638 // y 639 // z 640 641 if (dz > 0) { 642 current_angle1 = PI; 643 current_angle2 = PI+PI_2; 644 } else { 645 current_angle1 = 0; 646 current_angle2 = PI_2; 647 } 648 649 train_error1 = 0; 650 train_error2 = 0; 651 652 while(train_NN(current_angle1, train_error1, 0.1, arc_radius.z, arc_center.z, previous.z)) {} 653 while(train_NN(current_angle2, train_error2, 0.1, arc_radius.z, arc_center.z, location.z)) {} 654 655 if (current_angle1 > current_angle2 && dz > 0) { 656 arc_angle.z = (2*PI) - current_angle1; 657 arc_maxangle.z = (2*PI) - current_angle2; 658 } else { 659 arc_angle.z = current_angle1; 660 arc_maxangle.z = current_angle2; 661 } 662 663 // z 664 665 double angle_sum_third = (abs(dx)+abs(dy)+abs(dz))*(1/3f); 666 Point angle_diff = Point( 667 arc_maxangle.x-arc_angle.x, 668 arc_maxangle.y-arc_angle.y, 669 arc_maxangle.z-arc_angle.z); 670 arc_delta = Point( 671 copysign(angle_diff.x / angle_sum_third, angle_diff.x) * interval, 672 copysign(angle_diff.y / angle_sum_third, angle_diff.y) * interval, 673 copysign(angle_diff.z / angle_sum_third, angle_diff.z) * interval); 674 675 arc_angle.x += arc_delta.x; 676 arc_angle.y += arc_delta.y; 677 arc_angle.z += arc_delta.z; 678 break; 679 680 case Type.Bezier: 681 bezier_buffer.length = bezier_points.length+2; 682 origin_point = previous; 683 684 // Our goal for getting the number of ticks to operate for 685 // is to determine essentially the length of the line. 686 // Unfortunately this goal is impossible to do. 687 // So we do things a bit more evilly. 688 689 // And truth is, I don't care too much about it. 690 // What I care about is figuring out a tick count that will work for all of the arc's. 691 692 // Each bezier curve exists in a 0f..1f space. 693 // But we have three axis to work upon, not two. 694 // What we want to archive is to get the difference between each control point, 695 // is to be roughly the length of interval. 696 // Because of this, we treat each arc seperately, 697 // the distance we want, is (bestDistance/arcs.length) == interval 698 // we know that it won't be possible to get this to occur, 699 // but we want to get as close to it as we can. 700 701 double 702 bestDistance = double.max_exp, 703 bestTickCount = 1, 704 expectedDistance = interval*(bezier_points.length+1), 705 tickCount=1; 706 707 foreach(i; 0 .. 60) { 708 // 0..4 = 1 709 // 4..8 = 0.1 710 // 8..12 = 0.01 711 double 712 learnRate = 713 i < 20 ? interval : 714 i < 40 ? interval / 10 : 715 interval / 100; 716 Point sum = Point(0, 0, 0); 717 718 foreach(j; 0 .. 2) { 719 double tickIndevidual = 1f/tickCount; 720 double tick = j == 0 ? tickIndevidual : (2f/tickCount); 721 Point startPoint = previous; 722 723 foreach(p; bezier_points) { 724 sum.x += bezier_combine(startPoint.x, p.x, tick+tickIndevidual)-bezier_combine(startPoint.x, p.x, tick); 725 sum.y += bezier_combine(startPoint.y, p.y, tick+tickIndevidual)-bezier_combine(startPoint.y, p.y, tick); 726 sum.z += bezier_combine(startPoint.z, p.z, tick+tickIndevidual)-bezier_combine(startPoint.z, p.z, tick); 727 728 startPoint = p; 729 } 730 731 sum.x += bezier_combine(startPoint.x, location.x, tick+tickIndevidual)-bezier_combine(startPoint.x, location.x, tick); 732 sum.y += bezier_combine(startPoint.y, location.y, tick+tickIndevidual)-bezier_combine(startPoint.y, location.y, tick); 733 sum.z += bezier_combine(startPoint.z, location.z, tick+tickIndevidual)-bezier_combine(startPoint.z, location.z, tick); 734 } 735 736 double sumV = (sum.x+sum.y+sum.z)/6; 737 if (bestDistance - expectedDistance > sumV-expectedDistance) { 738 bestDistance = sumV; 739 bestTickCount = tickCount; 740 } else { 741 tickCount += tickCount * learnRate * (sumV / expectedDistance); 742 } 743 } 744 745 bezier_tick = 1f/(bestTickCount/2); 746 bezier_bezierOffset = bezier_tick; 747 break; 748 749 default: 750 break; 751 } 752 } 753 754 double dx, dy, dz; 755 Point origin_point; 756 union { 757 struct { 758 double[] bezier_buffer; 759 double bezier_tick, 760 bezier_bezierOffset; 761 } 762 763 struct { 764 Point arc_delta, 765 arc_center, 766 arc_maxangle, 767 arc_angle; 768 } 769 Point lineTo_delta; 770 } 771 772 Point nextCoord(double interval, ref Point previous) { 773 switch(type) { 774 case Type.Point: 775 scope(exit) location = Point.init; 776 return location; 777 778 case Type.LineTo: 779 if ((dx > 0 && previous.x >= location.x) || (dx < 0 && previous.x <= location.x) || 780 (dy > 0 && previous.y >= location.y) || (dy < 0 && previous.y <= location.y) || 781 (dz > 0 && previous.z >= location.z) || (dz < 0 && previous.z <= location.z)) 782 return Point.init; 783 else { 784 Point ret = Point( 785 previous.x + lineTo_delta.x, 786 previous.y + lineTo_delta.y, 787 previous.z + lineTo_delta.z); 788 789 if ((dx > 0 && ret.x >= location.x) || (dx < 0 && ret.x <= location.x)) 790 ret.x = location.x; 791 if ((dy > 0 && ret.y >= location.y) || (dy < 0 && ret.y <= location.y)) 792 ret.y = location.y; 793 if ((dz > 0 && ret.z >= location.z) || (dz < 0 && ret.z <= location.z)) 794 ret.z = location.z; 795 796 return ret; 797 } 798 799 case Type.Arc: 800 import std.math : cos, sin; 801 802 if (arc_angle.x >= arc_maxangle.x || 803 arc_angle.y >= arc_maxangle.y || 804 arc_angle.z >= arc_maxangle.z ) 805 return Point.init; 806 807 Point ret = Point( 808 (arc_radius.x * cos(arc_angle.x)) + arc_center.x, 809 (arc_radius.y * cos(arc_angle.y)) + arc_center.y, 810 (arc_radius.z * cos(arc_angle.z)) + arc_center.z); 811 812 arc_angle.x += arc_delta.x; 813 arc_angle.y += arc_delta.y; 814 arc_angle.z += arc_delta.z; 815 816 if (arc_angle.x >= arc_maxangle.x) 817 ret.x = location.x; 818 if (arc_angle.y >= arc_maxangle.y) 819 ret.y = location.y; 820 if (arc_angle.z >= arc_maxangle.z) 821 ret.z = location.z; 822 823 return ret; 824 825 case Type.Bezier: 826 import std.math : ceil; 827 828 if (bezier_bezierOffset >= 1f) 829 return Point.init; 830 831 Point ret; 832 833 // 834 835 bezier_buffer[0] = origin_point.x; 836 bezier_buffer[$-1] = location.x; 837 foreach(i, p; bezier_points) 838 bezier_buffer[1+i] = p.x; 839 840 bezier_handle(bezier_buffer.length, bezier_buffer, bezier_bezierOffset); 841 ret.x = bezier_buffer[0]; 842 843 // 844 845 bezier_buffer[0] = origin_point.y; 846 bezier_buffer[$-1] = location.y; 847 foreach(i, p; bezier_points) 848 bezier_buffer[1+i] = p.y; 849 850 bezier_handle(bezier_buffer.length, bezier_buffer, bezier_bezierOffset); 851 ret.y = bezier_buffer[0]; 852 853 // 854 855 bezier_buffer[0] = origin_point.z; 856 bezier_buffer[$-1] = location.z; 857 foreach(i, p; bezier_points) 858 bezier_buffer[1+i] = p.z; 859 860 bezier_handle(bezier_buffer.length, bezier_buffer, bezier_bezierOffset); 861 ret.z = bezier_buffer[0]; 862 863 /// 864 865 bezier_bezierOffset += bezier_tick; 866 867 if (bezier_bezierOffset >= 1f) { 868 ret.x = location.x; 869 ret.y = location.y; 870 ret.z = location.z; 871 } 872 return ret; 873 874 default: 875 return Point.init; 876 } 877 } 878 879 enum Type { 880 Error, 881 Point, 882 LineTo, 883 Bezier, 884 Arc 885 } 886 } 887 888 double bezier_combine(double a, double b, double t) { 889 return (a*(1-t)) + (b*t); 890 } 891 892 void bezier_handle(size_t recurseIndex, double[] bezier_buffer, double bezier_bezierOffset) { 893 while(recurseIndex > 1) { 894 uint newIndex; 895 foreach(actualIndex; 1 .. recurseIndex) { 896 double 897 a = bezier_buffer[actualIndex-1], 898 b = bezier_buffer[actualIndex]; 899 bezier_buffer[newIndex] = bezier_combine(a, b, bezier_bezierOffset); 900 newIndex++; 901 } 902 903 recurseIndex = newIndex; 904 } 905 } 906 907 bool train_NN(ref double cangle, ref double error, double train_rate, double radius, double center, double v) { 908 import std.math : cos, PI, abs; 909 910 error = (radius*cos(cangle)+center)-v; 911 cangle += error*train_rate; 912 return abs(error) > train_rate; 913 } 914 915 double calculateDifferenceOnAxis(double a, double b) { 916 import std.math : abs, copysign; 917 918 if (a >= 0 && b >= 0) { 919 } else if (a <= 0 && b <= 0) { 920 } else if (a > b) { 921 return abs(b)+a; 922 } else { 923 return -(abs(a)+b); 924 } 925 926 return a-b; 927 } 928 }